package gbench.sandbox.matlib;

import org.junit.jupiter.api.Test;

import java.awt.Color;
import java.text.NumberFormat;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

import gbench.common.matlib.MatlibCanvas;
import gbench.common.matlib.MatlibCanvas.NVec;
import gbench.common.matlib.MatlibCanvas.Range;
import gbench.common.matlib.MatlibCanvas.XVec;
import gbench.common.matlib.data.DataReader.DFrame;
import gbench.common.matlib.img.ImageOps;
import gbench.common.matlib.img.ImageWrapper;
import gbench.common.matlib.data.DataReader;
import gbench.common.tree.LittleTree.Elem;
import gbench.common.tree.LittleTree.IRecord;

import static java.lang.Math.PI;
import static gbench.common.matlib.MatlibCanvas.println;
import static gbench.common.matlib.MatlibCanvas.stateData;
import static gbench.common.tree.LittleTree.IRecord.REC;

/**
 * 
 * @author gbench
 *
 */
class PlotPanel {
    
    /**
     * 
     */
    public PlotPanel() {
        this(1000, 1000 / 2, Range.of2(100, 650), Range.of2(50, 450));
    }

    /**
     * 
     * @param width
     * @param height
     * @param rngX
     * @param rngY
     */
    public PlotPanel(final Integer width, final Integer height, final Range rngX, final Range rngY) {
        this.width = width;
        this.height = height;
        this.rngX = rngX;
        this.rngY = rngY;
    }
    
    /**
     * 绘制点线图
     * @param dataframe DataFrame 结构的IRecord,IRecord每个都是一个列向量。
     */
    public void pointLines(final DFrame dataframe) {
        pointLines((IRecord)dataframe.eval(e->e));
    }
    
    /**
     * 绘制点线图
     * @param dataframe DataFrame 结构的IRecord,IRecord每个都是一个列向量。
     */
    public void pointLines(final IRecord dataframe) {
        
         // 环境数据
        final var matlib = new MatlibCanvas(this.width); // 构造绘图环境,canvas
        
        // 基础数据
        final var rngX = this.rngX;// x 的绘图区域范围
        final var stateX = stateData(0d); // 转台数据
        final var raw_x0 = dataframe.lla(0,Object.class); // 数据空间的 水平采样点
        final var raw_x = dataframe.lla(0, x -> IRecord.obj2dbl(stateX.getAndUpdate(i -> i + 1)).apply(x)); // 数据空间的水平采样点
        final var x_transformer = DataReader.transformer(rngX, raw_x); // 水平坐标转换器
        final var rhotetas = Range.cph(Range.of2(0, 5).limit(10), Range.of2(-PI, PI).limit(40))
            .collect(Collectors.toList()); // 小圆点的绘制向量长度rho与角度theta
        
        // 横坐标数据
        final var xs = NVec.of(raw_x.stream().map(x_transformer)); // 转换成绘图水平坐标的点 :x values
        
        // 坐标轴参数
        final var ysState = new AtomicReference<NVec>();
        final var rngYState = new AtomicReference<Range>();
        final var rawYState = new AtomicReference<List<Double>>();
        
        // 线条数据
        final var lineS = dataframe.keys()
            .stream().skip(1) // 剔除第一列:第一列用作横坐标
            .map( fld -> { // 键名
                // 曲线
                final Range rngY = this.rngY;// y 的绘图区域范围
                final List<Double> raw_y = dataframe.lla(fld, IRecord.obj2dbl(0d)); // 数据空间的 垂直采样点
                final Function<Double, Double> y_transformer = DataReader.transformer(rngY, raw_y); // 垂直坐标转换器
                final NVec ys = NVec.of(raw_y.stream().map(y_transformer)); // 转换成绘图垂直坐标的点 :y values
                final List<NVec> points = xs.zip(ys).map(e -> NVec.of(e._1(),e._2())) // [[x,y]]
                    // .map(matlib::xY) // [[x,y,r,g,b]]
                    .collect(Collectors.toList());// cartesian形式的圆心位置序列
                
                // 设置绘图状态s
                ysState.set(ys);
                rngYState.set(rngY);
                rawYState.set(raw_y);
                
                // 绘制趋势线 的参数：即回归算法,注意由于是绘制直线，所以这里的算法采用的 绘图空间数据，而不是业务数据模型。
                // 所以这里使用的模型不是 业务的回归模型。
                final var xb = xs.mean(); // x 的平均数,x bar
                final var yb = ys.mean(); // y 的平均数 y bar
                final var xd = xs.sub(xb); // x的离差 x dispersion
                final var b1 = xs.sub(xb).dot(ys.sub(yb)) / xd.dot(xd); // 趋势线的斜率
                final var b0 = yb - b1 * xb;// 趋势线的截距
                final var line = REC("points", points, "b0", b0, "b1", b1); // 折线数据结构
                
                return line; // 折线数据
            }); // lineS
        
        // 开始直线绘图
        matlib.plot(this.width, this.height, pen -> { // 画笔会话
            
            lineS.forEach(line -> { // 绘制折线
                final var points = line.get("points", (List<NVec>) null); // 圆心点集合
                final var b0 = line.dbl("b0");
                final var b1 = line.dbl("b1");
                
                //设置画笔颜色
                final var rgb = ImageOps.rndrgb(); // 随机生成一个颜色
                pen.setColor(rgb);
                
                // 绘制圆心点
                pen.drawPoints(Elem.cph2(rhotetas, points).map(matlib::rhotheta_affine_offset)); // 绘制图像的点
                // 把圆心点连接成折线
                IRecord.slidingS(points, 2, 1, true).forEach(pen::drawStraightLine);// 将序列剪裁收尾相连的选段序列，并连接成线。
                
                // 绘制回归线
                pen.setColor(rgb).drawLine(x -> b0 + b1 * x);// 绘制回归线
            }); // 绘制折线
            
        }, null, true, img -> {// 坐标轴的绘制
            
            final var g = new ImageWrapper(img, true);// 包装图片类
            final var nf = NumberFormat.getInstance(); // 数据格式化器
            
            final var margin = 50; // 边际余量单位,用于绘制坐标轴的延展方向。
            final var ytick_offset = 30; // y轴刻度边际保留
            final var xtick_offset = -13; // x轴刻度边际保留
            final var ys = ysState.get();
            final var rngY = rngYState.get();
            final var raw_y = rawYState.get();
            
            // x水平轴
            g.setColor(Color.red).HL (rngX.offset(-margin,2*margin), rngY.start()); // X水平轴
            xs.foreach((i, x) -> {
                g.setColor(Color.red).drawString(nf.format(raw_x.get(i)), x, rngY.start() + xtick_offset);
                g.setColor(Color.red).drawString(raw_x0.get(i), x, rngY.start() + 2*xtick_offset);
            }); // 水平轴上的 业务数据值
            
            // 水平轴的刻度-标准
            final var xdtf = DataReader.detransformer(rngX, raw_x); // 绘图空间到数据空间的变换
            rngX.offsetEnd(1 * margin).limit(10).forEach(x -> {
                final var s = rngY.start();
                g.setColor(Color.white).drawString(nf.format(xdtf.apply(x)), x, s + 3 * xtick_offset);
                g.setColor(Color.gray).VL(Range.of(s + 3 * xtick_offset,s), x); // 刻度线
            });
            
            // y垂直轴
            g.setColor(Color.red).VL(rngY.offset(-margin,margin), rngX.getStart()); // 垂直轴
            ys.foreach((i, y) -> {
                g.setColor(Color.red).drawString(nf.format(raw_y.get(i)), rngX.start() - ytick_offset, y);
            }); // 垂直轴上的 业务数据值
            
            // 垂直轴的标准刻度
            final var ydtf = DataReader.detransformer(rngY, raw_y); // 绘图空间到数据空间的变换
            rngY.offsetEnd(3*margin).limit(10).forEach(y -> {
                final var s = rngX.start();
                g.setColor(Color.white).drawString(nf.format(ydtf.apply(y)), s - 3 * ytick_offset, y);
                g.setColor(Color.gray).HL(Range.of(s, s - 3 * ytick_offset), y); // 垂直轴
            });
            
        });// plot
        
    } // pointLines
    
    /**
     * 
     * @param width
     * @return
     */
    public PlotPanel width(final Number width) {
        return new PlotPanel(width.intValue(),this.height,rngX,this.rngY);
    }
    
    /**
     * 
     * @param height
     * @return
     */
    public PlotPanel height(final Number height) {
        return new PlotPanel(this.width,height.intValue(),rngX,this.rngY);
    }
    
    /**
     * 
     * @param rngX
     * @return
     */
    public PlotPanel rangeX(final Range rngX) {
        return new PlotPanel(this.width,this.height,rngX,this.rngY);
    }
    
    /**
     * 
     * @param rngU
     * @return
     */
    public PlotPanel rangeY(final Range rngY) {
        return new PlotPanel(this.width,this.height,this.rngX,rngY);
    }
    
    final Integer width;// 绘图的防范尺寸[0,size)
    final Integer height;// 绘图的防范尺寸[0,size)
    final Range rngY;// y 的绘图区域范围
    final Range rngX;// x 的绘图区域范围
}

/**
 * 数据迭代 的演示
 * 
 * @author gbench
 *
 */
public class JunitIterative {
    
    /**
     * 采用迭代法求解方程
     * 5*x = 20 <br>
     * 其解为 4 <br>
     * 迭代求解，变换方程为 (引入 参数 a) <br>
     * (5-a)*x = 50-a*x <br>
     * xk = (20-axk_1)/(5-a) <br>
     */
    @Test
    public void foo() {
        final BiFunction<Double, Double, Double> error =
                (x, standard) -> Math.abs(x - standard) / Math.abs(standard);
        final Double standard = 4d; // 标准值
        final Function<Integer, Function<Double, DFrame>> iterative_evaluator = (n) -> { // 迭代长度
            return a -> { // args 参数
                final var _xk = stateData(1d); // 迭代初始值,前值
                final var dfm = XVec.OF(n, k -> { // 迭代的长度
                    _xk.getAndUpdate(x -> k == 0 ? x : (20 - a * x) / (5 - a)); // 第 0 次 迭代不予计算
                    final var xk = _xk.get(); // 新生成的 xk
                    return REC("k", "t_"+k, "xk_"+a, xk, "error_"+a, error.apply(xk, standard));
                }).collect(DFrame.dfmclc); // 归集成数据表
                println(dfm);
                return dfm;
            }; // a -> args 参数
        }; // n -> 迭代长度
        final var vargs = XVec.OF(0.1, 0.5, 1, 2.2).fmap(Number::doubleValue); // 构造参数集合
        final var result = vargs.fmap(iterative_evaluator.apply(12))
            .reduce2((a, b) -> { // 列合并
                return a.cbind(b.name(2),b.col(2));
            }).filter2(1); // 去除第一列
        
        // 显示计算结果
        println(result);
        
        // 绘制曲线
        new PlotPanel().pointLines(result);
       
        try {
            Thread.sleep(50000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
}
